In a previous couple of tutorials, we created a Spring boot project, built CRUD Restful web services with DTO, and used the ModelMapper library to convert the JPA entity into DTO and vice versa.
Refer to previous tutorials:
Spring Boot 3 CRUD RESTful WebServices with MySQL Database
Spring Boot DTO Example Tutorial
Spring Boot ModelMapper Example - Map Entity to DTO
In this tutorial, we will learn how to use the MapStruct library to map the JPA entity into DTO and vice versa in the Spring boot application.
The MapStruct is an annotation-based code generator/mapper which greatly simplifies the mapping implementations of Java Beans. It follows convention over configuration and uses plain method invocations. MapStruct operations are very fast, type-safe, and easy to understand.
MapStruct automates the process of creating a mapper to map data objects with model objects using annotations. It creates a mapper implementation at compile time which helps the developer to figure out errors during development and makes it easy to understand.
Check the official doc to read more about MapStruct at https://mapstruct.org/
This tutorial is a continuation of below three tutorials so first, create CRUD REST APIs using below tutorials:
Spring Boot 3 CRUD RESTful WebServices with MySQL Database
Spring Boot DTO Example Tutorial
Spring Boot ModelMapper Example - Map Entity to DTO
The complete source code of this tutorial is available on my GitHub repository at Spring Boot CRUD RESTful WebServices
If you want to use the MapStruct library in your existing Spring boot project then follow these simple steps:
1. Add Maven Dependencies
2. User and UserDto Classes
3. Create UserMapper
4. Use UserMapper in Service Class to map the JPA entity into DTO and vice versa.
5. Test CRUD REST APIs using the Postman client
Open the pom.xml file and add below Maven dependency and plugin.
MapStruct maven dependency:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
Let’s also add the annotationProcessorPaths section to the configuration part of the maven-compiler-plugin plugin.
The mapstruct-processor is used to generate the mapper implementation during the build:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
Note that we have added below annotation processor path to the above plugin to support MapStruct annotations with Lombok annotations:
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
Next, add the version properties to the properties section:
<properties>
<java.version>17</java.version>
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
<org.projectlombok.version>1.18.20</org.projectlombok.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
Here is our User JPA entity:
package net.javaguides.springboot.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String firstName;
@Column(nullable = false)
private String lastName;
@Column(nullable = false, unique = true)
private String email;
}
Here is our UserDto class:
package net.javaguides.springboot.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
private Long id;
private String firstName;
private String lastName;
private String email;
}
Next, let's create a Mapper using MapStruct. Let's create an AutoUserMapper interface and define the mapping methods to map Entity to DTO and vice versa.
package net.javaguides.springboot.mapper;
import net.javaguides.springboot.dto.UserDto;
import net.javaguides.springboot.entity.User;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface AutoUserMapper {
AutoUserMapper MAPPER = Mappers.getMapper(AutoUserMapper.class);
UserDto mapToUserDto(User user);
User mapToUser(UserDto userDto);
}
Notice we did not create an implementation class for our AutoUserMapper interface because MapStruct creates it for us during compilation time.
The @Mapper annotation marks the interface as a mapping interface and lets the MapStruct processor kick in during compilation.
We defined two mapping methods to convert JPA entity into DTO and vice versa:
UserDto mapToUserDto(User user);
User mapToUser(UserDto userDto);
An instance of the interface implementation can be retrieved from the Mappers class:
AutoUserMapper MAPPER = Mappers.getMapper(AutoUserMapper.class);
From our previous example, MapStruct was able to map our beans automatically because they have the same field names. So, what if a bean we are about to map has a different field name?.
Consider email field name is different in both User and UserDto. For example:
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String firstName;
@Column(nullable = false)
private String lastName;
@Column(nullable = false, unique = true)
private String email;
}
The email field name in UserDto is emailAddress:
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
private Long id;
private String firstName;
private String lastName;
private String emailAddress;
}
When mapping different field names, we will need to configure its source field to its target field, and to do that, we will need to add @Mapping annotation for each field.
For example:
@Mapper
public interface AutoUserMapper {
AutoUserMapper MAPPER = Mappers.getMapper(AutoUserMapper.class);
@Mapping(source = "email", target = "emailAddress")
UserDto mapToUserDto(User user);
@Mapping(source = "emailAddress", target = "email")
User mapToUser(UserDto userDto);
}
package net.javaguides.springboot.service.impl;
import lombok.AllArgsConstructor;
import net.javaguides.springboot.dto.UserDto;
import net.javaguides.springboot.entity.User;
import net.javaguides.springboot.mapper.AutoUserMapper;
import net.javaguides.springboot.mapper.UserMapper;
import net.javaguides.springboot.repository.UserRepository;
import net.javaguides.springboot.service.UserService;
import org.apache.logging.log4j.util.Strings;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@AllArgsConstructor
public class UserServiceImpl implements UserService {
private UserRepository userRepository;
private ModelMapper modelMapper;
@Override
public UserDto createUser(UserDto userDto) {
// Convert UserDto into User JPA Entity
// User user = UserMapper.mapToUser(userDto);
//User user = modelMapper.map(userDto, User.class);
User user = AutoUserMapper.MAPPER.mapToUser(userDto);
User savedUser = userRepository.save(user);
// Convert User JPA entity to UserDto
//UserDto savedUserDto = UserMapper.mapToUserDto(savedUser);
//UserDto savedUserDto = modelMapper.map(savedUser, UserDto.class);
UserDto savedUserDto = AutoUserMapper.MAPPER.mapToUserDto(savedUser);
return savedUserDto;
}
@Override
public UserDto getUserById(Long userId) {
User user = userRepository.findById(userId).get();
//return UserMapper.mapToUserDto(user);
//return modelMapper.map(user, UserDto.class);
return AutoUserMapper.MAPPER.mapToUserDto(user);
}
@Override
public List getAllUsers() {
List users = userRepository.findAll();
// return users.stream().map(UserMapper::mapToUserDto)
// .collect(Collectors.toList());
// return users.stream().map((user) -> modelMapper.map(user, UserDto.class))
// .collect(Collectors.toList());
return users.stream().map((user) -> AutoUserMapper.MAPPER.mapToUserDto(user))
.collect(Collectors.toList());
}
@Override
public UserDto updateUser(UserDto user) {
User existingUser = userRepository.findById(user.getId()).get()
existingUser.setFirstName(user.getFirstName());
existingUser.setLastName(user.getLastName());
existingUser.setEmail(user.getEmail());
User updatedUser = userRepository.save(existingUser);
//return UserMapper.mapToUserDto(updatedUser);
//return modelMapper.map(updatedUser, UserDto.class);
return AutoUserMapper.MAPPER.mapToUserDto(updatedUser);
}
@Override
public void deleteUser(Long userId) {
userRepository.deleteById(userId);
}
}
Request URL: http://localhost:8080/api/users
HTTP Method: POST
Request Body:
{
"firstName": "ramesh",
"lastName":"fadatare",
"email": "ramesh@gmail.com"
}
Request URL: http://localhost:8080/api/users/1
HTTP Method: GET
Refer to this screenshot to test GET User REST API:
Request URL: http://localhost:8080/api/users/1
HTTP Method: PUT
Request Body:
{
"firstName": "ram",
"lastName":"fadatare",
"email": "ram@gmail.com"
}
Refer to this screenshot to test the Update User REST API:
Request URL: http://localhost:8080/api/users
HTTP Method: GET
Refer to this screenshot to test GET All User REST API:
Request URL: http://localhost:8080/api/users/1
HTTP Method: DELETE
Refer to this screenshot to test Delete User REST API:
The source code of this tutorial is available on my GitHub repository at Spring Boot CRUD RESTful WebServices
In this tutorial, we have seen how to use the MapStruct library to map the JPA entity into DTO and vice versa in the Spring boot application.